
/*
    TreeViewer - Cross-platform software to draw phylogenetic trees
    Copyright (C) 2023  Giorgio Bianchini, University of Bristol
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using PhyloTree;
using TreeViewer;
using VectSharp;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;

namespace a7ef1591643834ee7b4bdbd44a7be1849
{
    /// <summary>
    /// This module is used to highlight monophyletic groups by adding "bars" with labels next to the tree.
    /// </summary>
    /// 
    /// <description>
    /// <![CDATA[
    /// ## Further information
    /// 
    /// This module cannot detect automatically the best value for the various layout parameters, therefore you will have to tweak these manually. In particular, on very large trees it may seem at first that the module is
    /// not doing anything; if this is the case, please try increasing the [Font](#font) size or the other parameters.
    /// 
    /// Multiple instances of this module can be used to obtain various effects; e.g. one instance could be used to draw the labels, while another can be used to draw coloured bars.
    /// 
    /// If the [Prevent overlap](#prevent-overlap) option is enabled, the labels are arranged on multiple rows in order to prevent overlapping. The [Manual label rows](#manual-label-rows) option can be used to manually
    /// "override" the automatic row assignment.
    /// 
    /// Here are two examples of this module in action on a tree with _Rectangular_ and _Circular_ coordinates, respectively:
    /// 
    /// <p align="center">
    ///     <img height="500" src="">
    /// </p>
    ///
    /// <p align="center">
    ///     <img height="500" src="">
    /// </p>
    /// ]]>
    /// </description>
    public static class MyModule
    {
        public const string Name = "Group labels";
        public const string HelpText = "Highlights monophyletic groups with a label.";
        public const string Author = "Giorgio Bianchini";
        public static Version Version = new Version("1.6.0");
        public const ModuleTypes ModuleType = ModuleTypes.Plotting;

        public const string Id = "7ef15916-4383-4ee7-b4bd-bd44a7be1849";

        private static string Icon16Base64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACJSURBVDhPzZJBDoAgDATB+DovvsgI8UVe/J52m9ZA0WC44CSkS5HdRnXd8dOyn6KbGKQ2Uxgc2+xFfsIaRFOrZAaUHvBOUKWVgel00ZZDUgObWkwBc10acn8FuKoGdk9EvaTgfBTNaTJaCvorBC7bAFQ2sIcKHqI+G7yhE1Qho/Aw4Q/+xN44dwGDakGwtWqEegAAAABJRU5ErkJggg==";
        private static string Icon24Base64 = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGoSURBVEhL3ZXLK0RRHMevV1jMwmIW2EjyWCjWVmThUWzYmPgHsNHsNFmgvNb2s0JsbCYLImEhpCiSlBILj6nJZkr4/O45d7r3uNdiMovxrc98z2N+53vuOXcaK+9V0BPb/NLtnKhQe870vwN6oQGW7V6WCgpIJqb7E3BDe00NZaeggHX54A3rI2Sf5rX0s1FgAIsX44uqa61qN/UMUeiCVojAFmTkF3DPrrfxKagnqAwPOqZOOIE2GIYHarvxA7DlF2AfDxrVHqVIjkiOylQc9iAGE7ACol3t/gHsugavUF1rUrvfMbVoF71DmtpSPGmPIDPggt0e440wCzOwRFE17ndMLzAO1dSFoBbS9oyWGbCh/RDmYUGTovANd+YdyeXuQJxNpODDHnXJDJDjacdTBq8gcj/FLaGneAfIZYegiHrPmu7OEQVX+IjqelRC4QDz8gROWB1j5fg5XMIdRPjOJ56RO2CMArmgQdX9oSHt8uY4moMzFm3GmyDMGvJEGZn/B/LDCaumr56gUjU9eoQq1fTKvIPfFhf5LS7yXVxkBvy5ch6Q77Ksb763d6OtbjroAAAAAElFTkSuQmCC";
        private static string Icon32Base64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAI3SURBVFhH7ZVLiE5hGMcPuSxQFtgYY6OwkBIiNZlkFrNQbgvXrWRHyGJkNWkWIxvNLGZFzRDlFguyoJlQilLKNRvKZSEsxe//vc/zdW7vmU99+dLMr349zznfec/7fO/tJJNMeKZ091z7bXlLmGqxZfwXBWzGebgQn+lGMxmvgFd4F7/iB7yITWW8ArzDBRZbVsAZi2/xTkibQ1UBo/g8pMlunBXShkZB7frxEB7E03gbC1QV4B11WzxmUfd/hbSAzhQVuwKP4DkcwBOo9+zCDI0UcNTiSYs/cCSkBe7hMG7H8/gIx7AXhdpdCGkgVsBV/BTSZKNFsdJibBo2oTq9jHtxLa5HjYAUKrJOrAD/h3ssOoct3sCPIS2gTstYZvGNxRplBXxD/4cHLDr7LYrYKKT5iS/xAb7TDdC9OmUFpF88Ax/m7EQRWwcamR5sx9m4FDvwFIrMx6/sa6h5zMxTBU/R14XQaanVriN7De7ENtQWvoWDuAqfYI38COjo9c7VOKZGRuSnQVtOnWsXPEbtIG29LbgaC+QL8Bcux0sV+uLMT8N9ixsspplpMTPisQL2WYzhv2thpY9m/2a8t+joGZ2IBdIF6MDwo3erxRhaiItDmpkGb3cWd2AfbsMunIYF0gX4Wa197Hu2Cj9Wr1sUujeEi/AKHsebqO38Agvkd8F3nBPShtDzoqzNa/yC62pXgc84HefWriC/Bv6mc6HnY22WYLpzMR/rnYt8Af+clhcwyUQnSf4AyNdwldDnHqQAAAAASUVORK5CYII=";

        public static Page GetIcon(double scaling)
        {
            byte[] bytes;

            if (scaling <= 1)
            {

                bytes = Convert.FromBase64String(Icon16Base64);
            }
            else if (scaling <= 1.5)
            {
                bytes = Convert.FromBase64String(Icon24Base64);
            }
            else
            {
                bytes = Convert.FromBase64String(Icon32Base64);
            }

            IntPtr imagePtr = Marshal.AllocHGlobal(bytes.Length);
            Marshal.Copy(bytes, 0, imagePtr, bytes.Length);

            RasterImage icon;

            try
            {
                icon = new VectSharp.MuPDFUtils.RasterImageStream(imagePtr, bytes.Length, MuPDFCore.InputFileTypes.PNG);
            }
            catch (Exception ex)
            {
                throw ex.InnerException;
            }
            finally
            {
                Marshal.FreeHGlobal(imagePtr);
            }

            Page pag = new Page(16, 16);
            pag.Graphics.DrawRasterImage(0, 0, 16, 16, icon);

            return pag;
        }

        public static List<(string, string)> GetParameters(TreeNode tree)
        {
            return new List<(string, string)>()
            {
                ( "Attribute", "Group:4" ),

                /// <param name="Attribute:">
                /// This parameter specifies the attribute used to determine the text of the labels.
                /// </param>
                ( "Attribute:", "TextBox:" ),

                /// <param name="Attribute type:">
                /// This parameter specifies the type of the attribute used to determine the text of the labels. By default this is `String`. If the type chosen here does not correspond to the actual type of the attribute
                /// (e.g. `Number` is chosen for the `Name` attribute, or `String` is chosen for the `Length` attribute), no label is drawn. If the attribute has values with different types for different nodes, the label is only shown on nodes
                /// whose attribute type corresponds to the one chosen here.
                /// </param>
                ( "Attribute type:", "AttributeType:String"),

                /// <param name="Attribute format...">
                /// This parameter determines how the value of the selected attribute is used to determine the text of the label. By default, if the [Attribute type](#attribute-type) is `String` the text of the label corresponds to the value of the attribute,
                /// while if the [Attribute type](#attribute-type) is `Number` the text of the label corresponds to the number rounded to 2 significant digits.
                /// </param>
                ( "Attribute format...", "Formatter:[\"Attribute type:\"," + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConverters[0]) + ",\"true\"]" ),
                
                /// <param name="Only on last ancestor">
                /// If this check box is checked, the label is only shown for nodes that have a value for the specified [attribute](#attribute) that differs from their ancestor's value. For example, this makes it possible to
                /// use the Propagate attribute module to propagate an attribute (e.g. a gene name or a phylum) to the last common ancestor of all taxa that have that attribute (i.e. all orthologs of that gene or all members
                /// of that phylum), and then show a group label for that LCA.
                /// </param>
                ( "Only on last ancestor", "CheckBox:false" ),

                ( "Layout", "Group:6" ),

                /// <param name="Anchor:">
                /// This parameter determines the point from which the [distance](#distance) is computed. If this is set to `Default`, distances are computed from the root node for _Rectangular_ coordinates, and from the
                /// centre of the circle for _Circular_ coordinates. If this is set to `Farthest leaf`, distances are computed from the position of the farthest leaf descending from each node (excluding any tip labels).
                /// This parameter has no effect for _Radial_ coordinates (in this case, the reference point is always the farthest leaf). If this parameter is set to `Farthest leaf`, the row overlap options cannot be used.
                /// </param>
                ( "Anchor:", "ComboBox:0[\"Default\",\"Farthest leaf\"]"),
                
                /// <param name="Distance:" display="">
                /// This parameter determines the distance from the [anchor](#anchor) at which the labels are drawn.
                /// </param>
                ( "Distance:", "NumericUpDown:100[\"-Infinity\", \"Infinity\"]" ),
                
                /// <param name="Distance: ">
                /// This parameter determines the distance from the [anchor](#anchor) at which the labels are drawn.
                /// </param>
                ( "Distance: ", "NumericUpDownByNode:100[\"-Infinity\", \"Infinity\"," + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConvertersToDouble[1]) + ",\"LabelDistance\",\"Number\",\"true\"]" ),
                
                /// <param name="Margin:">
                /// This parameter determines the margin to use when drawing the label. If this is greater than 0, the label is larger than the leaves that belong to it, while if it is lower than 0, the label is smaller
                /// than the leaves that belong to it.
                /// </param>
                ( "Margin:", "NumericUpDown:5[\"-Infinity\",\"Infinity\"]" ),
                
                /// <param name="Height:">
                /// This parameter determines the height of the label.
                /// </param>
                ( "Height:", "NumericUpDown:20[\"0\",\"Infinity\"]" ),

                ( "Label rows", "Group:7" ),
                
                /// <param name="Prevent overlap">
                /// If this check box is checked, the module will make sure that no two labels overlap, by shifting some of the labels further away. This happens automatically; if you wish to have more control, disable this
                /// option and enable multiple instances of the module using different attributes.
                /// </param>
                ( "Prevent overlap", "CheckBox:true"),

                /// <param name="Preserve nesting">
                /// If [Prevent overlap](#prevent-overlap) is enabled, this parameter determines whether labels for descendant nodes are allowed to float above their ancestors.
                /// </param>
                ( "Preserve nesting", "CheckBox:false" ),

                /// <param name="Gravity">
                /// If [Prevent overlap](#prevent-overlap) is enabled, this parameter determines whether the labels are clustered towards the top or the bottom.
                /// </param>
                ( "Gravity", "ComboBox:0[\"Bottom\",\"Top\",\"None\"]" ),
                
                /// <param name="Manual label rows">
                /// If this option is enabled, label rows can be specified manually using an attribute.
                /// </param>
                ( "Manual label rows", "CheckBox:false"),
                
                /// <param name="Label rows:">
                /// If [Manual label rows](#manual-label-rows) is enabled, this parameter can be used to manually specify label rows. Row 1 is closest to the tree and row numbers increase going further away from the tree (unless [Invert arrangement](#invert-arrangement) is enabled). Attribute values &lt; 1 are ignored.
                /// </param>
                ( "Label rows:", "NumericUpDownByNode:0[\"0\",\"Infinity\"," + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConvertersToDouble[1]) + ",\"LabelRow\",\"Number\",\"true\"]" ),
                
                /// <param name="Row margin:">
                /// If [Prevent overlap](#prevent-overlap) is enabled, this parameter determines the margin between consecutive rows of labels.
                /// </param>
                ( "Row margin:", "NumericUpDown:5[\"-Infinity\",\"Infinity\"]" ),
                
                /// <param name="Invert arrangement">
                /// If [Prevent overlap](#prevent-overlap) is enabled, this parameter determines whether larger labels are closest or farthest from the tree.
                /// </param>
                ( "Invert arrangement", "CheckBox:false" ),

                ( "Background options", "Group:3" ),
                
                /// <param name="Fill colour:">
                /// This parameter determines the colour of the background of the label.
                /// </param>
                ( "Fill colour:", "ColourByNode:[" + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConvertersToColour[0]) + ",\"LabelFillColour\",\"String\",\"0\",\"0\",\"0\",\"0\",\"true\"]" ),
                
                /// <param name="Stroke colour:">
                /// This parameter determines the colour of the outline of the background of the label.
                /// </param>
                ( "Stroke colour:", "ColourByNode:[" + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConvertersToColour[0]) + ",\"LabelStrokeColour\",\"String\",\"0\",\"0\",\"0\",\"255\",\"true\"]" ),
                
                /// <param name="Stroke thickness:">
                /// This parameter determines the thickness of the outline of the background of the label.
                /// </param>
                ( "Stroke thickness:", "NumericUpDownByNode:0[\"0\",\"Infinity\"," + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConvertersToDouble[1]) + ",\"LabelThickness\",\"Number\",\"true\"]" ),

                ( "Text options", "Group:5" ),
                
                /// <param name="Font:">
                /// This parameter determines the font used to draw the labels.
                /// </param>
                ( "Font:", "Font:[\"Helvetica-Bold\",\"10\"]"),
                
                /// <param name="Colour:" display="Text colour">
                /// This parameter determines the colour of the text used to draw the labels.
                /// </param>
                ( "Colour:", "ColourByNode:[" + System.Text.Json.JsonSerializer.Serialize(Modules.DefaultAttributeConvertersToColour[0]) + ",\"Color\",\"String\",\"0\",\"0\",\"0\",\"255\",\"true\"]" ),
                
                /// <param name="Alignment:" display="Text alignment">
                /// This parameter determines the alignment of the text within the label.
                /// </param>
                ( "Alignment:", "ComboBox:1[\"Near\",\"Center\",\"Far\"]" ),
                
                /// <param name="Margin: " display="Text margin">
                /// This parameter determines the margin between the edge of the label and the start of the text.
                /// </param>
                ( "Margin: ", "NumericUpDown:0[\"-Infinity\",\"Infinity\"]" ),

                /// <param name="Overflow:">
                /// This parameter determines what to do when the the label text is larger than the group that should be highlighted. If this is `Expand label`
                /// (the default), the label is enlarged so that all the text can fit in it. If this is `Clip`, the excess text is clipped (hidden). If this is
                /// `Compress text`, the text is scaled horizontally so that it fits completely within the label.
                /// </param>
                ( "Overflow:", "ComboBox:0[\"Expand label\",\"Clip\",\"Compress text\"]")
            };
        }

        public static bool OnParameterChange(object tree, Dictionary<string, object> previousParameterValues, Dictionary<string, object> currentParameterValues, out Dictionary<string, ControlStatus> controlStatus, out Dictionary<string, object> parametersToChange)
        {
            controlStatus = new Dictionary<string, ControlStatus>();
            parametersToChange = new Dictionary<string, object>();

            double currDist = (double)currentParameterValues["Distance:"];
            NumberFormatterOptions currDistFO = (NumberFormatterOptions)currentParameterValues["Distance: "];

            if (currDist != currDistFO.DefaultValue)
            {
                if (currDist != (double)previousParameterValues["Distance:"] && currDistFO.DefaultValue == ((NumberFormatterOptions)previousParameterValues["Distance: "]).DefaultValue)
                {
                    currDistFO.DefaultValue = currDist;
                    parametersToChange["Distance: "] = currDistFO;
                }
                else if (currDist == (double)previousParameterValues["Distance:"])
                {
                    parametersToChange["Distance:"] = currDistFO.DefaultValue;
                }
            }

            if ((string)previousParameterValues["Attribute:"] != (string)currentParameterValues["Attribute:"])
            {
                string attributeName = (string)currentParameterValues["Attribute:"];

                string attrType = ((TreeNode)tree).GetAttributeType(attributeName);

                if (!string.IsNullOrEmpty(attrType) && (string)previousParameterValues["Attribute type:"] == (string)currentParameterValues["Attribute type:"] && (string)currentParameterValues["Attribute type:"] != attrType)
                {
                    parametersToChange.Add("Attribute type:", attrType);

                    if (previousParameterValues["Attribute format..."] == currentParameterValues["Attribute format..."])
                    {
                        if (attrType == "String")
                        {
                            parametersToChange.Add("Attribute format...", new FormatterOptions(Modules.DefaultAttributeConverters[0]) { Parameters = new object[] { Modules.DefaultAttributeConverters[0], true } });
                        }
                        else if (attrType == "Number")
                        {
                            parametersToChange.Add("Attribute format...", new FormatterOptions(Modules.DefaultAttributeConverters[1]) { Parameters = new object[] { 0, 2.0, 0.0, 0.0, false, true, Modules.DefaultAttributeConverters[1], true } });
                        }
                    }
                }
            }

            if ((string)previousParameterValues["Attribute type:"] != (string)currentParameterValues["Attribute type:"])
            {
                string attrType = (string)currentParameterValues["Attribute type:"];

                if (previousParameterValues["Attribute format..."] == currentParameterValues["Attribute format..."])
                {
                    if (attrType == "String")
                    {
                        parametersToChange.Add("Attribute format...", new FormatterOptions(Modules.DefaultAttributeConverters[0]) { Parameters = new object[] { Modules.DefaultAttributeConverters[0], true } });
                    }
                    else if (attrType == "Number")
                    {
                        parametersToChange.Add("Attribute format...", new FormatterOptions(Modules.DefaultAttributeConverters[1]) { Parameters = new object[] { 0, 2.0, 0.0, 0.0, false, true, Modules.DefaultAttributeConverters[1], true } });
                    }
                }
            }

            if ((int)currentParameterValues["Anchor:"] == 0)
            {
                controlStatus["Label rows"] = ControlStatus.Enabled;

                if ((bool)currentParameterValues["Manual label rows"])
                {
                    controlStatus["Label rows:"] = ControlStatus.Enabled;
                }
                else
                {
                    controlStatus["Label rows:"] = ControlStatus.Hidden;
                }

                if ((bool)currentParameterValues["Prevent overlap"])
                {
                    controlStatus["Gravity"] = ControlStatus.Enabled;
                    controlStatus["Preserve nesting"] = ControlStatus.Enabled;
                }
                else
                {
                    controlStatus["Gravity"] = ControlStatus.Hidden;
                    controlStatus["Preserve nesting"] = ControlStatus.Hidden;
                }

                if ((bool)currentParameterValues["Prevent overlap"] || (bool)currentParameterValues["Manual label rows"])
                {
                    controlStatus["Row margin:"] = ControlStatus.Enabled;
                    controlStatus["Invert arrangement"] = ControlStatus.Enabled;

                    controlStatus["Distance:"] = ControlStatus.Enabled;
                    controlStatus["Distance: "] = ControlStatus.Hidden;
                }
                else
                {
                    controlStatus["Row margin:"] = ControlStatus.Hidden;
                    controlStatus["Invert arrangement"] = ControlStatus.Hidden;

                    controlStatus["Distance:"] = ControlStatus.Hidden;
                    controlStatus["Distance: "] = ControlStatus.Enabled;
                }
            }
            else
            {
                controlStatus["Label rows"] = ControlStatus.Hidden;
                controlStatus["Distance:"] = ControlStatus.Hidden;
                controlStatus["Distance: "] = ControlStatus.Enabled;
            }

            return true;
        }

        public static Point[] PlotAction(TreeNode tree, Dictionary<string, object> parameterValues, Dictionary<string, Point> coordinates, Graphics graphics)
        {
            Point topLeft = new Point(double.MaxValue, double.MaxValue);
            Point bottomRight = new Point(double.MinValue, double.MinValue);

            bool anyPoint = false;

            void updateMaxMin(Point pt)
            {
                anyPoint = true;
                topLeft = new Point(Math.Min(pt.X, topLeft.X), Math.Min(pt.Y, topLeft.Y));
                bottomRight = new Point(Math.Max(pt.X, bottomRight.X), Math.Max(pt.Y, bottomRight.Y));
            }

            string attributeName = (string)parameterValues["Attribute:"];
            string attributeType = (string)parameterValues["Attribute type:"];
            Func<object, string> attributeFormatter = ((FormatterOptions)parameterValues["Attribute format..."]).Formatter;

            NumberFormatterOptions distanceFO = (NumberFormatterOptions)parameterValues["Distance: "];
            double fixedDistance = (double)parameterValues["Distance:"];

            int textAlignment = (int)parameterValues["Alignment:"];
            double margin = (double)parameterValues["Margin:"];
            double textMargin = (double)parameterValues["Margin: "];
            double height = (double)parameterValues["Height:"];

            bool onlyOnLastAncestor = (bool)parameterValues["Only on last ancestor"];

            ColourFormatterOptions Fill = (ColourFormatterOptions)parameterValues["Fill colour:"];
            Colour defaultFill = Fill.DefaultColour;
            Func<object, Colour?> fillFormatter = Fill.Formatter;

            ColourFormatterOptions Stroke = (ColourFormatterOptions)parameterValues["Stroke colour:"];
            Colour defaultStroke = Stroke.DefaultColour;
            Func<object, Colour?> strokeFormatter = Stroke.Formatter;

            ColourFormatterOptions TextColour = (ColourFormatterOptions)parameterValues["Colour:"];
            Colour defaultTextColour = TextColour.DefaultColour;
            Func<object, Colour?> textColourFormatter = TextColour.Formatter;

            NumberFormatterOptions ThicknessFO = (NumberFormatterOptions)parameterValues["Stroke thickness:"];
            double defaultThickness = ThicknessFO.DefaultValue;
            Func<object, double?> thicknessFormatter = ThicknessFO.Formatter;


            Font labelFont = (Font)parameterValues["Font:"];

            int overflowMode = (int)parameterValues["Overflow:"];

            bool preventOverlap = (bool)parameterValues["Prevent overlap"];
            double rowMargin = (double)parameterValues["Row margin:"];
            int gravity = (int)parameterValues["Gravity"];
            bool invertArrangement = (bool)parameterValues["Invert arrangement"];
            bool preserveNesting = (bool)parameterValues["Preserve nesting"];
            bool manualLabelRows = (bool)parameterValues["Manual label rows"];
            NumberFormatterOptions labelRowFO = (NumberFormatterOptions)parameterValues["Label rows:"];

            int anchor = (int)parameterValues["Anchor:"];

            List<TreeNode> nodes = tree.GetChildrenRecursive();

            Point coordinateReference;

            if (coordinates.TryGetValue("68e25ec6-5911-4741-8547-317597e1b792", out coordinateReference))
            {
                // Rectangular coordinates

                Point rootPoint = coordinates[tree.Id];

                double coordModulus = Math.Sqrt(coordinateReference.X * coordinateReference.X + coordinateReference.Y * coordinateReference.Y);

                coordinateReference = new Point(coordinateReference.X / coordModulus, coordinateReference.Y / coordModulus);
                double angle = Math.Atan2(coordinateReference.Y, coordinateReference.X);
                while (angle < 0)
                {
                    angle += 2 * Math.PI;
                }

                Point perpReference = new Point(-coordinateReference.Y, coordinateReference.X);

                double perpAngle = Math.Atan2(perpReference.Y, perpReference.X);

                while (perpAngle < 0)
                {
                    perpAngle += 2 * Math.PI;
                }

                List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)> labels = new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>();

                for (int nodeIndex = 0; nodeIndex < nodes.Count; nodeIndex++)
                {
                    string attributeValue = null;

                    if (nodes[nodeIndex].Attributes.TryGetValue(attributeName, out object attributeObject) && attributeObject != null)
                    {
                        if (!onlyOnLastAncestor || nodes[nodeIndex].Parent == null || !nodes[nodeIndex].Parent.Attributes.TryGetValue(attributeName, out object attributeObjectParent) || attributeObjectParent == null || !attributeObjectParent.Equals(attributeObject))
                        {
                            attributeValue = attributeFormatter(attributeObject);
                        }
                    }

                    if (!string.IsNullOrEmpty(attributeValue))
                    {
                        Colour fill = defaultFill;
                        if (nodes[nodeIndex].Attributes.TryGetValue(Fill.AttributeName, out object fillAttributeObject) && fillAttributeObject != null)
                        {
                            fill = fillFormatter(fillAttributeObject) ?? defaultFill;
                        }

                        Colour stroke = defaultStroke;
                        if (nodes[nodeIndex].Attributes.TryGetValue(Stroke.AttributeName, out object strokeAttributeObject) && strokeAttributeObject != null)
                        {
                            stroke = strokeFormatter(strokeAttributeObject) ?? defaultStroke;
                        }

                        double strokeThickness = defaultThickness;
                        if (nodes[nodeIndex].Attributes.TryGetValue(ThicknessFO.AttributeName, out object thicknessAttributeObject) && thicknessAttributeObject != null)
                        {
                            strokeThickness = thicknessFormatter(thicknessAttributeObject) ?? defaultThickness;
                        }


                        List<TreeNode> leaves = nodes[nodeIndex].GetLeaves();

                        Point p1 = coordinates[leaves[0].Id];
                        Point p2 = coordinates[leaves[^1].Id];

                        double d1 = (p1.X - rootPoint.X) * perpReference.X + (p1.Y - rootPoint.Y) * perpReference.Y - margin;
                        double d2 = (p2.X - rootPoint.X) * perpReference.X + (p2.Y - rootPoint.Y) * perpReference.Y + margin;

                        if (fill.A == 0 && (stroke.A == 0 || strokeThickness == 0))
                        {
                            if (textAlignment == 0)
                            {
                                d2 = d1;
                            }
                            else if (textAlignment == 1)
                            {
                                double center = (d1 + d2) * 0.5;
                                d1 = center;
                                d2 = center;
                            }
                            else if (textAlignment == 2)
                            {
                                d1 = d2;
                            }
                        }

                        double textWidth = labelFont.MeasureText(attributeValue).Width + textMargin * 2;


                        if (textWidth > d2 - d1)
                        {
                            if (overflowMode == 0)
                            {
                                if (textAlignment == 0)
                                {
                                    d2 = d1 + textWidth;
                                }
                                else if (textAlignment == 1)
                                {
                                    double center = (d1 + d2) * 0.5;
                                    d1 = center - textWidth * 0.5;
                                    d2 = center + textWidth * 0.5;
                                }
                                else if (textAlignment == 2)
                                {
                                    d1 = d2 - textWidth;
                                }
                            }
                        }

                        Colour textColour = defaultTextColour;
                        if (nodes[nodeIndex].Attributes.TryGetValue(TextColour.AttributeName, out object textColourAttributeObject) && textColourAttributeObject != null)
                        {
                            textColour = textColourFormatter(textColourAttributeObject) ?? defaultTextColour;
                        }

                        labels.Add((d1, d2, fill, stroke, strokeThickness, textColour, attributeValue, nodeIndex));
                    }
                }

                List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>> sortedLabels;

                if (preventOverlap && anchor == 0)
                {
                    sortedLabels = SortLabels(labels, gravity, invertArrangement, preserveNesting, nodes, manualLabelRows ? labelRowFO : null);
                }
                else
                {
                    sortedLabels = new List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>> { labels };

                    if (manualLabelRows && anchor == 0)
                    {
                        for (int i = 0; i < sortedLabels.Count; i++)
                        {
                            for (int j = 0; j < sortedLabels[i].Count; j++)
                            {
                                double rowIndex = labelRowFO.DefaultValue;
                                if (nodes[sortedLabels[i][j].nodeIndex].Attributes.TryGetValue(labelRowFO.AttributeName, out object rowAttributeObject) && rowAttributeObject != null)
                                {
                                    rowIndex = labelRowFO.Formatter(rowAttributeObject) ?? labelRowFO.DefaultValue;
                                }

                                if (rowIndex >= 1)
                                {
                                    int newIndex = (int)(rowIndex - 1);

                                    if (newIndex != i)
                                    {
                                        while (newIndex >= sortedLabels.Count)
                                        {
                                            sortedLabels.Add(new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>());
                                        }

                                        sortedLabels[newIndex].Add(sortedLabels[i][j]);
                                        sortedLabels[i].RemoveAt(j);
                                        j--;
                                    }
                                }
                            }
                        }

                        for (int i = 0; i < sortedLabels.Count; i++)
                        {
                            if (sortedLabels[i].Count == 0)
                            {
                                sortedLabels.RemoveAt(i);
                                i--;
                            }
                        }

                        if (invertArrangement)
                        {
                            sortedLabels.Reverse();
                        }
                    }
                }

                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    double levelDepth = (height + rowMargin) * i;

                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        string nodeId = nodes[sortedLabels[i][j].nodeIndex].Id;
                        double d1 = sortedLabels[i][j].start;
                        double d2 = sortedLabels[i][j].end;
                        string attributeValue = sortedLabels[i][j].text;

                        Colour backgroundColor = sortedLabels[i][j].background;
                        Colour strokeColour = sortedLabels[i][j].stroke;
                        double backgroundStrokeThickness = sortedLabels[i][j].strokeThickness;
                        Colour labelColour = sortedLabels[i][j].textColour;

                        if (backgroundColor.A > 0 || (strokeColour.A > 0 && backgroundStrokeThickness > 0) || (labelColour.A > 0 && labelFont.FontSize > 0))
                        {
                            double dist;

                            if ((preventOverlap || manualLabelRows) && anchor == 0)
                            {
                                dist = fixedDistance;
                            }
                            else
                            {
                                dist = distanceFO.DefaultValue;

                                if (nodes[sortedLabels[i][j].nodeIndex].Attributes.TryGetValue(distanceFO.AttributeName, out object distanceAttrObject) && distanceAttrObject != null)
                                {
                                    dist = distanceFO.Formatter(distanceAttrObject) ?? distanceFO.DefaultValue;
                                }
                            }

                            if (anchor == 1)
                            {
                                double ptDist = ((coordinates[nodeId].X - rootPoint.X) * coordinateReference.X + (coordinates[nodeId].Y - rootPoint.Y) * coordinateReference.Y);

                                foreach (TreeNode leaf in nodes[sortedLabels[i][j].nodeIndex].GetLeaves())
                                {
                                    ptDist = Math.Max(ptDist, ((coordinates[leaf.Id].X - rootPoint.X) * coordinateReference.X + (coordinates[leaf.Id].Y - rootPoint.Y) * coordinateReference.Y));
                                }

                                dist = dist + ptDist;
                            }


                            Point c1 = new Point(rootPoint.X + (dist + levelDepth) * coordinateReference.X + d1 * perpReference.X, rootPoint.Y + (dist + levelDepth) * coordinateReference.Y + d1 * perpReference.Y);
                            Point c2 = new Point(rootPoint.X + (dist + height + levelDepth) * coordinateReference.X + d1 * perpReference.X, rootPoint.Y + (dist + height + levelDepth) * coordinateReference.Y + d1 * perpReference.Y);
                            Point c3 = new Point(rootPoint.X + (dist + height + levelDepth) * coordinateReference.X + d2 * perpReference.X, rootPoint.Y + (dist + height + levelDepth) * coordinateReference.Y + d2 * perpReference.Y);
                            Point c4 = new Point(rootPoint.X + (dist + levelDepth) * coordinateReference.X + d2 * perpReference.X, rootPoint.Y + (dist + levelDepth) * coordinateReference.Y + d2 * perpReference.Y);

                            updateMaxMin(c1);
                            updateMaxMin(c2);
                            updateMaxMin(c3);
                            updateMaxMin(c4);

                            GraphicsPath background = new GraphicsPath().MoveTo(c1).LineTo(c2).LineTo(c3).LineTo(c4).Close();

                            if (backgroundColor.A > 0)
                            {
                                graphics.FillPath(background, backgroundColor, tag: nodeId);
                            }

                            if (strokeColour.A > 0 && backgroundStrokeThickness > 0)
                            {
                                graphics.StrokePath(background, strokeColour, backgroundStrokeThickness, tag: nodeId);
                            }

                            graphics.Save();

                            if (labelFont.FontSize > 0)
                            {
                                double textWidth = labelFont.MeasureText(attributeValue).Width + textMargin * 2;

                                double xScale = 1;

                                if (textWidth > d2 - d1)
                                {
                                    if (overflowMode == 1)
                                    {
                                        graphics.SetClippingPath(background);
                                    }
                                    else if (overflowMode == 2)
                                    {
                                        xScale = (d2 - d1) / textWidth;
                                    }
                                }


                                if (angle > 0 && angle < Math.PI)
                                {
                                    if (textAlignment == 0)
                                    {
                                        graphics.Translate((c1.X + c2.X) * 0.5, (c1.Y + c2.Y) * 0.5);
                                        graphics.Rotate(perpAngle + Math.PI);
                                        graphics.Scale(xScale, 1);
                                        graphics.FillText(-textMargin - labelFont.MeasureText(attributeValue).Width, 0, attributeValue, labelFont, labelColour, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 1)
                                    {
                                        graphics.Translate((c1.X + c3.X) * 0.5, (c1.Y + c3.Y) * 0.5);
                                        graphics.Rotate(perpAngle + Math.PI);
                                        graphics.Scale(xScale, 1);
                                        graphics.FillText(-labelFont.MeasureText(attributeValue).Width * 0.5, 0, attributeValue, labelFont, labelColour, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 2)
                                    {
                                        graphics.Translate((c3.X + c4.X) * 0.5, (c3.Y + c4.Y) * 0.5);
                                        graphics.Rotate(perpAngle + Math.PI);
                                        graphics.Scale(xScale, 1);
                                        graphics.FillText(textMargin, 0, attributeValue, labelFont, labelColour, TextBaselines.Middle, tag: nodeId);
                                    }
                                }
                                else
                                {
                                    if (textAlignment == 0)
                                    {
                                        graphics.Translate((c1.X + c2.X) * 0.5, (c1.Y + c2.Y) * 0.5);
                                        graphics.Rotate(perpAngle);
                                        graphics.Scale(xScale, 1);
                                        graphics.FillText(textMargin, 0, attributeValue, labelFont, labelColour, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 1)
                                    {
                                        graphics.Translate((c1.X + c3.X) * 0.5, (c1.Y + c3.Y) * 0.5);
                                        graphics.Rotate(perpAngle);
                                        graphics.Scale(xScale, 1);
                                        graphics.FillText(-labelFont.MeasureText(attributeValue).Width * 0.5, 0, attributeValue, labelFont, labelColour, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 2)
                                    {
                                        graphics.Translate((c3.X + c4.X) * 0.5, (c3.Y + c4.Y) * 0.5);
                                        graphics.Rotate(perpAngle);
                                        graphics.Scale(xScale, 1);
                                        graphics.FillText(-textMargin - labelFont.MeasureText(attributeValue).Width, 0, attributeValue, labelFont, labelColour, TextBaselines.Middle, tag: nodeId);
                                    }
                                }
                            }

                            graphics.Restore();
                        }
                    }
                }
            }
            else if (coordinates.TryGetValue("92aac276-3af7-4506-a263-7220e0df5797", out coordinateReference))
            {
                // Circular coordinates

                List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)> labels = new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>();

                for (int nodeIndex = 0; nodeIndex < nodes.Count; nodeIndex++)
                {
                    string attributeValue = "";

                    if (nodes[nodeIndex].Attributes.TryGetValue(attributeName, out object attributeObject) && attributeObject != null)
                    {
                        if (!onlyOnLastAncestor || nodes[nodeIndex].Parent == null || !nodes[nodeIndex].Parent.Attributes.TryGetValue(attributeName, out object attributeObjectParent) || attributeObjectParent == null || !attributeObjectParent.Equals(attributeObject))
                        {
                            attributeValue = attributeFormatter(attributeObject);
                        }
                    }

                    if (!string.IsNullOrEmpty(attributeValue))
                    {
                        Colour fill = defaultFill;
                        if (nodes[nodeIndex].Attributes.TryGetValue(Fill.AttributeName, out object fillAttributeObject) && fillAttributeObject != null)
                        {
                            fill = fillFormatter(fillAttributeObject) ?? defaultFill;
                        }

                        Colour stroke = defaultStroke;
                        if (nodes[nodeIndex].Attributes.TryGetValue(Stroke.AttributeName, out object strokeAttributeObject) && strokeAttributeObject != null)
                        {
                            stroke = strokeFormatter(strokeAttributeObject) ?? defaultStroke;
                        }

                        double strokeThickness = defaultThickness;
                        if (nodes[nodeIndex].Attributes.TryGetValue(ThicknessFO.AttributeName, out object thicknessAttributeObject) && thicknessAttributeObject != null)
                        {
                            strokeThickness = thicknessFormatter(thicknessAttributeObject) ?? defaultThickness;
                        }

                        double dist;

                        if ((preventOverlap || manualLabelRows) && anchor == 0)
                        {
                            dist = fixedDistance;
                        }
                        else
                        {
                            dist = distanceFO.DefaultValue;

                            if (nodes[nodeIndex].Attributes.TryGetValue(distanceFO.AttributeName, out object distanceAttrObject) && distanceAttrObject != null)
                            {
                                dist = distanceFO.Formatter(distanceAttrObject) ?? distanceFO.DefaultValue;
                            }
                        }

                        if (anchor == 1)
                        {
                            double ptDist = coordinates[nodes[nodeIndex].Id].Modulus();

                            foreach (TreeNode leaf in nodes[nodeIndex].GetLeaves())
                            {
                                ptDist = Math.Max(ptDist, coordinates[leaf.Id].Modulus());
                            }

                            dist = dist + ptDist;
                        }

                        List<TreeNode> leaves = nodes[nodeIndex].GetLeaves();

                        Point p1 = coordinates[leaves[0].Id];
                        Point p2 = coordinates[leaves[^1].Id];

                        double d1 = Math.Atan2(p1.Y, p1.X);
                        double d2 = Math.Atan2(p2.Y, p2.X);

                        if (d2 < d1)
                        {
                            d2 += 2 * Math.PI;
                        }

                        d1 -= margin / (dist + height * 0.5);
                        d2 += margin / (dist + height * 0.5);

                        while (d1 < 0)
                        {
                            d1 += 2 * Math.PI;
                            d2 += 2 * Math.PI;
                        }

                        if (d2 > d1 + 2 * Math.PI)
                        {
                            d1 = 0.0001;
                            d2 = 2 * Math.PI + 0.0001;
                        }

                        if (fill.A == 0 && (stroke.A == 0 || strokeThickness == 0))
                        {
                            if (textAlignment == 0)
                            {
                                d2 = d1;
                            }
                            else if (textAlignment == 1)
                            {
                                double center = (d1 + d2) * 0.5;
                                d1 = center;
                                d2 = center;
                            }
                            else if (textAlignment == 2)
                            {
                                d1 = d2;
                            }
                        }

                        double origD1 = d1;
                        double origD2 = d2;

                        double textWidth = (labelFont.MeasureText(attributeValue).Width + textMargin * 2) / (dist + height * 0.5);

                        if (textWidth > d2 - d1)
                        {
                            if (overflowMode == 0)
                            {
                                if (textAlignment == 0)
                                {
                                    d2 = d1 + textWidth;
                                }
                                else if (textAlignment == 1)
                                {
                                    double center = (d1 + d2) * 0.5;
                                    d1 = center - textWidth * 0.5;
                                    d2 = center + textWidth * 0.5;
                                }
                                else if (textAlignment == 2)
                                {
                                    d1 = d2 - textWidth;
                                }
                            }
                        }

                        while (d1 < 0)
                        {
                            d1 += 2 * Math.PI;
                            d2 += 2 * Math.PI;
                        }

                        Colour textColour = defaultTextColour;
                        if (nodes[nodeIndex].Attributes.TryGetValue(TextColour.AttributeName, out object textColourAttributeObject) && textColourAttributeObject != null)
                        {
                            textColour = textColourFormatter(textColourAttributeObject) ?? defaultTextColour;
                        }


                        labels.Add((d1, d2, fill, stroke, strokeThickness, textColour, attributeValue, nodeIndex, origD1, origD2));


                    }
                }

                List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>> sortedLabels;

                if (preventOverlap && anchor == 0)
                {
                    sortedLabels = SortLabels(labels, gravity, invertArrangement, preserveNesting, nodes, manualLabelRows ? labelRowFO : null);
                }
                else
                {
                    sortedLabels = new List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>> { labels };

                    if (manualLabelRows && anchor == 0)
                    {
                        for (int i = 0; i < sortedLabels.Count; i++)
                        {
                            for (int j = 0; j < sortedLabels[i].Count; j++)
                            {
                                double rowIndex = labelRowFO.DefaultValue;
                                if (nodes[sortedLabels[i][j].nodeIndex].Attributes.TryGetValue(labelRowFO.AttributeName, out object rowAttributeObject) && rowAttributeObject != null)
                                {
                                    rowIndex = labelRowFO.Formatter(rowAttributeObject) ?? labelRowFO.DefaultValue;
                                }

                                if (rowIndex >= 1)
                                {
                                    int newIndex = (int)(rowIndex - 1);

                                    if (newIndex != i)
                                    {
                                        while (newIndex >= sortedLabels.Count)
                                        {
                                            sortedLabels.Add(new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>());
                                        }

                                        sortedLabels[newIndex].Add(sortedLabels[i][j]);
                                        sortedLabels[i].RemoveAt(j);
                                        j--;
                                    }
                                }
                            }
                        }

                        for (int i = 0; i < sortedLabels.Count; i++)
                        {
                            if (sortedLabels[i].Count == 0)
                            {
                                sortedLabels.RemoveAt(i);
                                i--;
                            }
                        }

                        if (invertArrangement)
                        {
                            sortedLabels.Reverse();
                        }
                    }
                }

                if ((preventOverlap || manualLabelRows) && anchor == 0)
                {
                    updateMaxMin(new Point(-fixedDistance - height - (height + rowMargin) * (sortedLabels.Count - 1), -fixedDistance - height - (height + rowMargin) * (sortedLabels.Count - 1)));
                    updateMaxMin(new Point(fixedDistance + height + (height + rowMargin) * (sortedLabels.Count - 1), fixedDistance + height + (height + rowMargin) * (sortedLabels.Count - 1)));
                }

                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        double dist;

                        if ((preventOverlap || manualLabelRows) && anchor == 0)
                        {
                            dist = fixedDistance;
                        }
                        else
                        {
                            dist = distanceFO.DefaultValue;

                            if (nodes[sortedLabels[i][j].nodeIndex].Attributes.TryGetValue(distanceFO.AttributeName, out object distanceAttrObject) && distanceAttrObject != null)
                            {
                                dist = distanceFO.Formatter(distanceAttrObject) ?? distanceFO.DefaultValue;
                            }
                        }

                        if (anchor == 1)
                        {
                            double ptDist = coordinates[nodes[sortedLabels[i][j].nodeIndex].Id].Modulus();

                            foreach (TreeNode leaf in nodes[sortedLabels[i][j].nodeIndex].GetLeaves())
                            {
                                ptDist = Math.Max(ptDist, coordinates[leaf.Id].Modulus());
                            }

                            dist = dist + ptDist;
                        }


                        double radius = dist + (height + rowMargin) * i;

                        string nodeId = nodes[sortedLabels[i][j].nodeIndex].Id;
                        double d1 = sortedLabels[i][j].originalStart;
                        double d2 = sortedLabels[i][j].originalEnd;
                        string attributeValue = sortedLabels[i][j].text;

                        double textWidth = (labelFont.MeasureText(attributeValue).Width + textMargin * 2) / (radius + height * 0.5);

                        bool shouldClip = false;

                        if (textWidth > d2 - d1)
                        {
                            if (overflowMode == 0)
                            {
                                if (textAlignment == 0)
                                {
                                    d2 = d1 + textWidth;
                                }
                                else if (textAlignment == 1)
                                {
                                    double center = (d1 + d2) * 0.5;
                                    d1 = center - textWidth * 0.5;
                                    d2 = center + textWidth * 0.5;
                                }
                                else if (textAlignment == 2)
                                {
                                    d1 = d2 - textWidth;
                                }
                            }
                            else
                            {
                                shouldClip = true;
                            }
                        }

                        while (d1 < 0)
                        {
                            d1 += 2 * Math.PI;
                            d2 += 2 * Math.PI;
                        }

                        Colour backgroundColor = sortedLabels[i][j].background;
                        Colour strokeColour = sortedLabels[i][j].stroke;
                        double backgroundStrokeThickness = sortedLabels[i][j].strokeThickness;
                        Colour labelColour = sortedLabels[i][j].textColour;

                        if (backgroundColor.A > 0 || (strokeColour.A > 0 && backgroundStrokeThickness > 0) || (labelColour.A > 0 && labelFont.FontSize > 0))
                        {
                            Point c1 = new Point(radius * Math.Cos(d1), radius * Math.Sin(d1));
                            Point c2 = new Point(radius * Math.Cos(d2), radius * Math.Sin(d2));
                            Point c3 = new Point((radius + height) * Math.Cos(d2), (radius + height) * Math.Sin(d2));
                            Point c4 = new Point((radius + height) * Math.Cos(d1), (radius + height) * Math.Sin(d1));

                            updateMaxMin(c1);
                            updateMaxMin(c2);
                            updateMaxMin(c3);
                            updateMaxMin(c4);

                            GraphicsPath background = new GraphicsPath().MoveTo(c1).Arc(0, 0, radius, d1, d2).LineTo(c3).Arc(0, 0, radius + height, d2, d1).Close();

                            if (backgroundColor.A > 0)
                            {
                                graphics.FillPath(background, backgroundColor, tag: nodeId);
                            }

                            if (strokeColour.A > 0 && backgroundStrokeThickness > 0)
                            {
                                graphics.StrokePath(background, strokeColour, backgroundStrokeThickness, tag: nodeId);
                            }

                            double startAngle = d1;
                            double endAngle = d2;

                            if (textAlignment == 0)
                            {
                                startAngle = d1 + (textMargin / dist);
                                endAngle = d1 + ((textMargin + labelFont.MeasureText(attributeValue).Width) / dist);
                            }
                            else if (textAlignment == 1)
                            {
                                startAngle = endAngle = (d1 + d2) * 0.5;
                            }
                            else if (textAlignment == 2)
                            {
                                startAngle = d2 - (textMargin / dist);
                                endAngle = d2 - ((textMargin + labelFont.MeasureText(attributeValue).Width) / dist);
                            }



                            double angle = (startAngle + endAngle) * 0.5;

                            while (angle < 0)
                            {
                                angle += 2 * Math.PI;
                            }

                            angle = angle % (2 * Math.PI);

                            graphics.Save();

                            if (labelFont.FontSize > 0)
                            {
                                if (shouldClip)
                                {
                                    graphics.SetClippingPath(background);
                                }

                                if (angle > 0 && angle < Math.PI)
                                {
                                    GraphicsPath arc = new GraphicsPath().Arc(0, 0, radius + height * 0.5, d2, d1);

                                    if (textAlignment == 2)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Left, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 1)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 0.5, TextAnchors.Center, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 0)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 1 - (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Right, TextBaselines.Middle, tag: nodeId);
                                    }
                                }
                                else
                                {
                                    GraphicsPath arc = new GraphicsPath().Arc(0, 0, radius + height * 0.5, d1, d2);

                                    if (textAlignment == 0)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Left, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 1)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 0.5, TextAnchors.Center, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 2)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 1 - (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Right, TextBaselines.Middle, tag: nodeId);
                                    }
                                }
                            }


                            graphics.Restore();
                        }
                    }
                }
            }
            else if (coordinates.TryGetValue("95b61284-b870-48b9-b51c-3276f7d89df1", out coordinateReference))
            {
                // Radial coordinates

                for (int nodeIndex = 0; nodeIndex < nodes.Count; nodeIndex++)
                {
                    string attributeValue = "";

                    if (nodes[nodeIndex].Attributes.TryGetValue(attributeName, out object attributeObject) && attributeObject != null)
                    {
                        if (!onlyOnLastAncestor || nodes[nodeIndex].Parent == null || !nodes[nodeIndex].Parent.Attributes.TryGetValue(attributeName, out object attributeObjectParent) || attributeObjectParent == null || !attributeObjectParent.Equals(attributeObject))
                        {
                            attributeValue = attributeFormatter(attributeObject);
                        }
                    }

                    if (!string.IsNullOrEmpty(attributeValue))
                    {
                        Colour fill = defaultFill;
                        if (nodes[nodeIndex].Attributes.TryGetValue(Fill.AttributeName, out object fillAttributeObject) && fillAttributeObject != null)
                        {
                            fill = fillFormatter(fillAttributeObject) ?? defaultFill;
                        }

                        Colour stroke = defaultStroke;
                        if (nodes[nodeIndex].Attributes.TryGetValue(Stroke.AttributeName, out object strokeAttributeObject) && strokeAttributeObject != null)
                        {
                            stroke = strokeFormatter(strokeAttributeObject) ?? defaultStroke;
                        }

                        double strokeThickness = defaultThickness;
                        if (nodes[nodeIndex].Attributes.TryGetValue(ThicknessFO.AttributeName, out object thicknessAttributeObject) && thicknessAttributeObject != null)
                        {
                            strokeThickness = thicknessFormatter(thicknessAttributeObject) ?? defaultThickness;
                        }


                        List<TreeNode> leaves = nodes[nodeIndex].GetLeaves();
                        Point parentPoint = coordinates[nodes[nodeIndex].Id];

                        double d1 = double.MaxValue;
                        double d2 = double.MinValue;

                        double distance = distanceFO.DefaultValue;

                        if (nodes[nodeIndex].Attributes.TryGetValue(distanceFO.AttributeName, out object distanceAttributeObject) && distanceAttributeObject != null)
                        {
                            distance = distanceFO.Formatter(distanceAttributeObject) ?? distanceFO.DefaultValue;
                        }

                        double radius = distance + height * 0.5;

                        foreach (TreeNode leaf in leaves)
                        {
                            Point pt = coordinates[leaf.Id];

                            double angle = Math.Atan2(pt.Y - parentPoint.Y, pt.X - parentPoint.X);

                            d1 = Math.Min(d1, angle);
                            d2 = Math.Max(d2, angle);

                            radius = Math.Max(radius, Math.Sqrt((pt.X - parentPoint.X) * (pt.X - parentPoint.X) + (pt.Y - parentPoint.Y) * (pt.Y - parentPoint.Y)) + distance + height * 0.5);
                        }

                        if (d2 < d1)
                        {
                            d2 += 2 * Math.PI;
                        }

                        d1 -= margin / radius;
                        d2 += margin / radius;

                        while (d1 < 0)
                        {
                            d1 += 2 * Math.PI;
                            d2 += 2 * Math.PI;
                        }

                        if (d2 > d1 + 2 * Math.PI)
                        {
                            d1 = 0.0001;
                            d2 = 2 * Math.PI + 0.0001;
                        }

                        if (fill.A == 0 && (stroke.A == 0 || strokeThickness == 0))
                        {
                            if (textAlignment == 0)
                            {
                                d2 = d1;
                            }
                            else if (textAlignment == 1)
                            {
                                double center = (d1 + d2) * 0.5;
                                d1 = center;
                                d2 = center;
                            }
                            else if (textAlignment == 2)
                            {
                                d1 = d2;
                            }
                        }

                        double origD1 = d1;
                        double origD2 = d2;

                        double textWidth = (labelFont.MeasureText(attributeValue).Width + textMargin * 2) / radius;
                        bool shouldClip = false;

                        if (textWidth > d2 - d1)
                        {
                            if (overflowMode == 0)
                            {
                                if (textAlignment == 0)
                                {
                                    d2 = d1 + textWidth;
                                }
                                else if (textAlignment == 1)
                                {
                                    double center = (d1 + d2) * 0.5;
                                    d1 = center - textWidth * 0.5;
                                    d2 = center + textWidth * 0.5;
                                }
                                else if (textAlignment == 2)
                                {
                                    d1 = d2 - textWidth;
                                }
                            }
                            else
                            {
                                shouldClip = true;
                            }
                        }

                        while (d1 < 0)
                        {
                            d1 += 2 * Math.PI;
                            d2 += 2 * Math.PI;
                        }

                        Colour textColour = defaultTextColour;
                        if (nodes[nodeIndex].Attributes.TryGetValue(TextColour.AttributeName, out object textColourAttributeObject) && textColourAttributeObject != null)
                        {
                            textColour = textColourFormatter(textColourAttributeObject) ?? defaultTextColour;
                        }

                        Colour backgroundColor = fill;
                        Colour strokeColour = stroke;
                        double backgroundStrokeThickness = strokeThickness;
                        Colour labelColour = textColour;
                        string nodeId = nodes[nodeIndex].Id;
                        radius -= height;

                        if (backgroundColor.A > 0 || (strokeColour.A > 0 && backgroundStrokeThickness > 0) || (labelColour.A > 0 && labelFont.FontSize > 0))
                        {
                            Point c1 = new Point(radius * Math.Cos(d1) + parentPoint.X, radius * Math.Sin(d1) + parentPoint.Y);
                            Point c2 = new Point(radius * Math.Cos(d2) + parentPoint.X, radius * Math.Sin(d2) + parentPoint.Y);
                            Point c3 = new Point((radius + height) * Math.Cos(d2) + parentPoint.X, (radius + height) * Math.Sin(d2) + parentPoint.Y);
                            Point c4 = new Point((radius + height) * Math.Cos(d1) + parentPoint.X, (radius + height) * Math.Sin(d1) + parentPoint.Y);

                            updateMaxMin(c1);
                            updateMaxMin(c2);
                            updateMaxMin(c3);
                            updateMaxMin(c4);

                            GraphicsPath background = new GraphicsPath().MoveTo(c1).Arc(parentPoint, radius, d1, d2).LineTo(c3).Arc(parentPoint, radius + height, d2, d1).Close();

                            if (backgroundColor.A > 0)
                            {
                                graphics.FillPath(background, backgroundColor, tag: nodeId);
                            }

                            if (strokeColour.A > 0 && backgroundStrokeThickness > 0)
                            {
                                graphics.StrokePath(background, strokeColour, backgroundStrokeThickness, tag: nodeId);
                            }

                            double startAngle = d1;
                            double endAngle = d2;

                            if (textAlignment == 0)
                            {
                                startAngle = d1 + (textMargin / distance);
                                endAngle = d1 + ((textMargin + labelFont.MeasureText(attributeValue).Width) / distance);
                            }
                            else if (textAlignment == 1)
                            {
                                startAngle = endAngle = (d1 + d2) * 0.5;
                            }
                            else if (textAlignment == 2)
                            {
                                startAngle = d2 - (textMargin / distance);
                                endAngle = d2 - ((textMargin + labelFont.MeasureText(attributeValue).Width) / distance);
                            }

                            double angle = (startAngle + endAngle) * 0.5;

                            while (angle < 0)
                            {
                                angle += 2 * Math.PI;
                            }

                            angle = angle % (2 * Math.PI);

                            graphics.Save();

                            if (labelFont.FontSize > 0)
                            {
                                if (shouldClip)
                                {
                                    graphics.SetClippingPath(background);
                                }

                                if (angle > 0 && angle < Math.PI)
                                {
                                    GraphicsPath arc = new GraphicsPath().Arc(parentPoint, radius + height * 0.5, d2, d1);

                                    if (textAlignment == 2)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Left, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 1)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 0.5, TextAnchors.Center, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 0)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 1 - (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Right, TextBaselines.Middle, tag: nodeId);
                                    }
                                }
                                else
                                {
                                    GraphicsPath arc = new GraphicsPath().Arc(parentPoint, radius + height * 0.5, d1, d2);

                                    if (textAlignment == 0)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Left, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 1)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 0.5, TextAnchors.Center, TextBaselines.Middle, tag: nodeId);
                                    }
                                    else if (textAlignment == 2)
                                    {
                                        graphics.FillTextOnPath(arc, attributeValue, labelFont, labelColour, 1 - (textMargin / (radius + height * 0.5)) / (d2 - d1), TextAnchors.Right, TextBaselines.Middle, tag: nodeId);
                                    }
                                }
                            }

                            graphics.Restore();
                        }
                    }
                }
            }
            else
            {
                throw new Exception("The Coordinates module is not supported!");
            }

            if (!anyPoint)
            {
                topLeft = bottomRight = new Point();
            }

            if (parameterValues.TryGetValue(Modules.WarningMessageControlID, out object action) && action is Action<string, string> setWarning)
            {
                StringBuilder warningMessage = new StringBuilder();

                if (!coordinates.ContainsKey("68e25ec6-5911-4741-8547-317597e1b792") && overflowMode == 2)
                {
                    warningMessage.AppendLine("Label text can only be compressed when using Rectangular coordinates.");
                }

                if (coordinates.ContainsKey("95b61284-b870-48b9-b51c-3276f7d89df1") && preventOverlap)
                {
                    if (warningMessage.Length > 0)
                    {
                        warningMessage.AppendLine();
                    }

                    warningMessage.AppendLine("Group label overlap cannot be prevented when using Radial coordinates.");
                }

                if (coordinates.ContainsKey("95b61284-b870-48b9-b51c-3276f7d89df1") && manualLabelRows)
                {
                    if (warningMessage.Length > 0)
                    {
                        warningMessage.AppendLine();
                    }

                    warningMessage.AppendLine("Label rows are ignored when using Radial coordinates.");
                }

                setWarning(warningMessage.ToString(), Id);
            }

            return new Point[] { topLeft, bottomRight };
        }

        static bool Overlap((double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex) a, (double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex) b)
        {
            return a.start <= b.end && b.start <= a.end;
        }

        static bool Overlap((double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd) a, (double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd) b)
        {
            double asn = a.start;
            double aen = a.end;
            double bsn = b.start;
            double ben = b.end;

            while (asn < 0)
            {
                asn += 2 * Math.PI;
            }

            while (aen < 0)
            {
                aen += 2 * Math.PI;
            }

            while (bsn < 0)
            {
                bsn += 2 * Math.PI;
            }

            while (ben < 0)
            {
                ben += 2 * Math.PI;
            }

            asn = asn % (2 * Math.PI);
            aen = aen % (2 * Math.PI);
            bsn = bsn % (2 * Math.PI);
            ben = ben % (2 * Math.PI);

            return Intersect(bsn, asn, aen) || Intersect(ben, asn, aen) || Intersect(asn, bsn, ben) || Intersect(aen, bsn, ben);
        }

        static bool Intersect(double b, double aS, double aE)
        {
            if (aS > aE)
            {
                if (b >= aS || b <= aE)
                {
                    return true;
                }
            }
            else if (b >= aS && b <= aE)
            {
                return true;
            }
            return false;
        }

        static bool DescendsFrom(this TreeNode a, TreeNode b)
        {
            TreeNode currParent = a;

            while (currParent != null)
            {
                if (currParent == b)
                {
                    return true;
                }
                currParent = currParent.Parent;
            }

            return false;
        }

        static List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>> SortLabels(List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)> labels, int gravity, bool invertArrangement, bool preserveNesting, List<TreeNode> nodes, NumberFormatterOptions manualRows)
        {
            List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>> sortedLabels = new List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>>();

            if (preserveNesting)
            {
                labels.Sort((a, b) =>
                {
                    if (a.nodeIndex != b.nodeIndex && nodes[a.nodeIndex].DescendsFrom(nodes[b.nodeIndex]))
                    {
                        return 1;
                    }
                    else if (a.nodeIndex != b.nodeIndex && nodes[b.nodeIndex].DescendsFrom(nodes[a.nodeIndex]))
                    {
                        return -1;
                    }
                    else
                    {
                        return Math.Sign(a.start - b.start);
                    }
                });
            }
            else
            {
                labels.Sort((a, b) =>
                {
                    return Math.Sign(a.start - b.start);
                });
            }

            Dictionary<string, int> labelLevels = new Dictionary<string, int>();

            for (int i = 0; i < labels.Count; i++)
            {
                bool found = false;

                int minJ = 0;
                if (preserveNesting)
                {
                    TreeNode parent = nodes[labels[i].nodeIndex].Parent;

                    while (parent != null)
                    {
                        if (labelLevels.TryGetValue(parent.Id, out int lev))
                        {
                            minJ = Math.Max(minJ, lev);
                            break;
                        }
                        parent = parent.Parent;
                    }
                }

                for (int j = minJ; j < sortedLabels.Count; j++)
                {
                    bool overlapping = false;

                    for (int k = 0; k < sortedLabels[j].Count; k++)
                    {
                        if (Overlap(labels[i], sortedLabels[j][k]))
                        {
                            overlapping = true;
                            break;
                        }
                    }

                    if (!overlapping)
                    {
                        sortedLabels[j].Add(labels[i]);
                        if (preserveNesting)
                        {
                            string id = nodes[labels[i].nodeIndex].Id;
                            labelLevels.TryGetValue(id, out int prevLevel);
                            labelLevels[id] = Math.Max(j, prevLevel);
                        }
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)> newRow = new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>() { labels[i] };

                    sortedLabels.Add(newRow);

                    if (preserveNesting)
                    {
                        string id = nodes[labels[i].nodeIndex].Id;
                        labelLevels.TryGetValue(id, out int prevLevel);
                        labelLevels[id] = Math.Max(sortedLabels.Count - 1, prevLevel);
                    }
                }
            }

            if (preserveNesting)
            {
                sortedLabels.Sort((a, b) =>
                {
                    double totA = 0;
                    for (int i = 0; i < a.Count; i++)
                    {
                        totA += a[i].end - a[i].start;

                        for (int j = 0; j < b.Count; j++)
                        {
                            if (nodes[a[i].nodeIndex].DescendsFrom(nodes[b[j].nodeIndex]))
                            {
                                return -1;
                            }
                        }
                    }

                    double totB = 0;
                    for (int i = 0; i < b.Count; i++)
                    {
                        totB += b[i].end - b[i].start;

                        for (int j = 0; j < a.Count; j++)
                        {
                            if (nodes[b[i].nodeIndex].DescendsFrom(nodes[a[j].nodeIndex]))
                            {
                                return 1;
                            }
                        }
                    }

                    return Math.Sign(totA - totB);
                });

                labelLevels.Clear();

                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        labelLevels[nodes[sortedLabels[i][j].nodeIndex].Id] = i;
                    }
                }
            }
            else
            {
                sortedLabels.Sort((a, b) =>
                {
                    double totA = 0;
                    for (int i = 0; i < a.Count; i++)
                    {
                        totA += a[i].end - a[i].start;
                    }

                    double totB = 0;
                    for (int i = 0; i < b.Count; i++)
                    {
                        totB += b[i].end - b[i].start;
                    }

                    return Math.Sign(totA - totB);
                });
            }

            if (gravity == 0)
            {
                for (int i = 1; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        int maxK = i;
                        if (preserveNesting)
                        {
                            TreeNode parent = nodes[sortedLabels[i][j].nodeIndex].Parent;

                            while (parent != null)
                            {
                                if (labelLevels.TryGetValue(parent.Id, out int lev))
                                {
                                    maxK = Math.Min(maxK, lev);
                                    break;
                                }
                                parent = parent.Parent;
                            }
                        }

                        for (int k = 0; k < maxK; k++)
                        {
                            bool overlapping = false;

                            for (int l = 0; l < sortedLabels[k].Count; l++)
                            {
                                if (Overlap(sortedLabels[i][j], sortedLabels[k][l]))
                                {
                                    overlapping = true;
                                    break;
                                }
                            }

                            if (!overlapping)
                            {
                                labelLevels[nodes[sortedLabels[i][j].nodeIndex].Id] = k;
                                sortedLabels[k].Add(sortedLabels[i][j]);
                                sortedLabels[i].RemoveAt(j);
                                j--;
                                break;
                            }
                        }
                    }
                }
            }
            else if (gravity == 1)
            {
                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        int maxK = sortedLabels.Count;
                        if (preserveNesting)
                        {
                            TreeNode parent = nodes[sortedLabels[i][j].nodeIndex].Parent;

                            while (parent != null)
                            {
                                if (labelLevels.TryGetValue(parent.Id, out int lev))
                                {
                                    maxK = Math.Min(maxK, lev);
                                    break;
                                }
                                parent = parent.Parent;
                            }
                        }

                        for (int k = i + 1; k < maxK; k++)
                        {
                            bool overlapping = false;

                            for (int l = 0; l < sortedLabels[k].Count; l++)
                            {
                                if (Overlap(sortedLabels[i][j], sortedLabels[k][l]))
                                {
                                    overlapping = true;
                                    break;
                                }
                            }

                            if (!overlapping)
                            {
                                labelLevels[nodes[sortedLabels[i][j].nodeIndex].Id] = k;
                                sortedLabels[k].Add(sortedLabels[i][j]);
                                sortedLabels[i].RemoveAt(j);
                                j--;
                                break;
                            }
                        }
                    }
                }
            }

            for (int i = 0; i < sortedLabels.Count; i++)
            {
                if (sortedLabels[i].Count == 0)
                {
                    sortedLabels.RemoveAt(i);
                    i--;
                }
            }

            if (manualRows != null)
            {
                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        double rowIndex = manualRows.DefaultValue;
                        if (nodes[sortedLabels[i][j].nodeIndex].Attributes.TryGetValue(manualRows.AttributeName, out object rowAttributeObject) && rowAttributeObject != null)
                        {
                            rowIndex = manualRows.Formatter(rowAttributeObject) ?? manualRows.DefaultValue;
                        }

                        if (rowIndex >= 1)
                        {
                            int newIndex = (int)(rowIndex - 1);

                            if (newIndex != i)
                            {
                                while (newIndex >= sortedLabels.Count)
                                {
                                    sortedLabels.Add(new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex)>());
                                }

                                sortedLabels[newIndex].Add(sortedLabels[i][j]);
                                sortedLabels[i].RemoveAt(j);
                                j--;
                            }
                        }
                    }
                }

                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    if (sortedLabels[i].Count == 0)
                    {
                        sortedLabels.RemoveAt(i);
                        i--;
                    }
                }
            }

            if (invertArrangement)
            {
                sortedLabels.Reverse();
            }

            return sortedLabels;
        }

        static List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>> SortLabels(List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)> labels, int gravity, bool invertArrangement, bool preserveNesting, List<TreeNode> nodes, NumberFormatterOptions manualRows)
        {
            List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>> sortedLabels = new List<List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>>();

            if (preserveNesting)
            {
                labels.Sort((a, b) =>
                {
                    if (a.nodeIndex != b.nodeIndex && nodes[a.nodeIndex].DescendsFrom(nodes[b.nodeIndex]))
                    {
                        return 1;
                    }
                    else if (a.nodeIndex != b.nodeIndex && nodes[b.nodeIndex].DescendsFrom(nodes[a.nodeIndex]))
                    {
                        return -1;
                    }
                    else
                    {
                        return Math.Sign(a.start - b.start);
                    }
                });
            }
            else
            {
                labels.Sort((a, b) =>
                {
                    return Math.Sign(a.start - b.start);
                });
            }

            Dictionary<string, int> labelLevels = new Dictionary<string, int>();

            for (int i = 0; i < labels.Count; i++)
            {
                bool found = false;

                int minJ = 0;
                if (preserveNesting)
                {
                    TreeNode parent = nodes[labels[i].nodeIndex].Parent;

                    while (parent != null)
                    {
                        if (labelLevels.TryGetValue(parent.Id, out int lev))
                        {
                            minJ = Math.Max(minJ, lev);
                            break;
                        }
                        parent = parent.Parent;
                    }
                }

                for (int j = minJ; j < sortedLabels.Count; j++)
                {
                    bool overlapping = false;

                    for (int k = 0; k < sortedLabels[j].Count; k++)
                    {
                        if (Overlap(labels[i], sortedLabels[j][k]))
                        {
                            overlapping = true;
                            break;
                        }
                    }

                    if (!overlapping)
                    {
                        sortedLabels[j].Add(labels[i]);
                        if (preserveNesting)
                        {
                            string id = nodes[labels[i].nodeIndex].Id;
                            labelLevels.TryGetValue(id, out int prevLevel);
                            labelLevels[id] = Math.Max(j, prevLevel);
                        }
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)> newRow = new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>() { labels[i] };

                    sortedLabels.Add(newRow);

                    if (preserveNesting)
                    {
                        string id = nodes[labels[i].nodeIndex].Id;
                        labelLevels.TryGetValue(id, out int prevLevel);
                        labelLevels[id] = Math.Max(sortedLabels.Count - 1, prevLevel);
                    }
                }
            }

            if (preserveNesting)
            {
                sortedLabels.Sort((a, b) =>
                {
                    double totA = 0;
                    for (int i = 0; i < a.Count; i++)
                    {
                        totA += a[i].end - a[i].start;

                        for (int j = 0; j < b.Count; j++)
                        {
                            if (nodes[a[i].nodeIndex].DescendsFrom(nodes[b[j].nodeIndex]))
                            {
                                return -1;
                            }
                        }
                    }

                    double totB = 0;
                    for (int i = 0; i < b.Count; i++)
                    {
                        totB += b[i].end - b[i].start;

                        for (int j = 0; j < a.Count; j++)
                        {
                            if (nodes[b[i].nodeIndex].DescendsFrom(nodes[a[j].nodeIndex]))
                            {
                                return 1;
                            }
                        }
                    }

                    return Math.Sign(totA - totB);
                });

                labelLevels.Clear();

                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        labelLevels[nodes[sortedLabels[i][j].nodeIndex].Id] = i;
                    }
                }
            }
            else
            {
                sortedLabels.Sort((a, b) =>
                {
                    double totA = 0;
                    for (int i = 0; i < a.Count; i++)
                    {
                        totA += a[i].end - a[i].start;
                    }

                    double totB = 0;
                    for (int i = 0; i < b.Count; i++)
                    {
                        totB += b[i].end - b[i].start;
                    }

                    return Math.Sign(totA - totB);
                });
            }

            if (gravity == 0)
            {
                for (int i = 1; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        int maxK = i;
                        if (preserveNesting)
                        {
                            TreeNode parent = nodes[sortedLabels[i][j].nodeIndex].Parent;

                            while (parent != null)
                            {
                                if (labelLevels.TryGetValue(parent.Id, out int lev))
                                {
                                    maxK = Math.Min(maxK, lev);
                                    break;
                                }
                                parent = parent.Parent;
                            }
                        }

                        for (int k = 0; k < maxK; k++)
                        {
                            bool overlapping = false;

                            for (int l = 0; l < sortedLabels[k].Count; l++)
                            {
                                if (Overlap(sortedLabels[i][j], sortedLabels[k][l]))
                                {
                                    overlapping = true;
                                    break;
                                }
                            }

                            if (!overlapping)
                            {
                                labelLevels[nodes[sortedLabels[i][j].nodeIndex].Id] = k;
                                sortedLabels[k].Add(sortedLabels[i][j]);
                                sortedLabels[i].RemoveAt(j);
                                j--;
                                break;
                            }
                        }
                    }
                }
            }
            else if (gravity == 1)
            {
                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        int maxK = sortedLabels.Count;
                        if (preserveNesting)
                        {
                            TreeNode parent = nodes[sortedLabels[i][j].nodeIndex].Parent;

                            while (parent != null)
                            {
                                if (labelLevels.TryGetValue(parent.Id, out int lev))
                                {
                                    maxK = Math.Min(maxK, lev);
                                    break;
                                }
                                parent = parent.Parent;
                            }
                        }

                        for (int k = i + 1; k < maxK; k++)
                        {
                            bool overlapping = false;

                            for (int l = 0; l < sortedLabels[k].Count; l++)
                            {
                                if (Overlap(sortedLabels[i][j], sortedLabels[k][l]))
                                {
                                    overlapping = true;
                                    break;
                                }
                            }

                            if (!overlapping)
                            {
                                labelLevels[nodes[sortedLabels[i][j].nodeIndex].Id] = k;
                                sortedLabels[k].Add(sortedLabels[i][j]);
                                sortedLabels[i].RemoveAt(j);
                                j--;
                                break;
                            }
                        }
                    }
                }
            }

            for (int i = 0; i < sortedLabels.Count; i++)
            {
                if (sortedLabels[i].Count == 0)
                {
                    sortedLabels.RemoveAt(i);
                    i--;
                }
            }

            if (manualRows != null)
            {
                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    for (int j = 0; j < sortedLabels[i].Count; j++)
                    {
                        double rowIndex = manualRows.DefaultValue;
                        if (nodes[sortedLabels[i][j].nodeIndex].Attributes.TryGetValue(manualRows.AttributeName, out object rowAttributeObject) && rowAttributeObject != null)
                        {
                            rowIndex = manualRows.Formatter(rowAttributeObject) ?? manualRows.DefaultValue;
                        }

                        if (rowIndex >= 1)
                        {
                            int newIndex = (int)(rowIndex - 1);

                            if (newIndex != i)
                            {
                                while (newIndex >= sortedLabels.Count)
                                {
                                    sortedLabels.Add(new List<(double start, double end, Colour background, Colour stroke, double strokeThickness, Colour textColour, string text, int nodeIndex, double originalStart, double originalEnd)>());
                                }

                                sortedLabels[newIndex].Add(sortedLabels[i][j]);
                                sortedLabels[i].RemoveAt(j);
                                j--;
                            }
                        }
                    }
                }

                for (int i = 0; i < sortedLabels.Count; i++)
                {
                    if (sortedLabels[i].Count == 0)
                    {
                        sortedLabels.RemoveAt(i);
                        i--;
                    }
                }
            }

            if (invertArrangement)
            {
                sortedLabels.Reverse();
            }

            return sortedLabels;
        }
    }
}

